﻿using System;
using System.Data;
using System.Linq;
using Common.Data.Sql;
using Dqaqs.Common;
using Dqaqs.Configuration;
using Dqaqs.Services.Data;

namespace Dqaqs.Implemenation.Basic
{
	/// <summary>
	/// Executes the query against a data source. This class does not support queries that span across
	/// more than one table.
	/// </summary>
	public class BasicQueryExecuter : IQueryExecuter
	{
		private readonly IDal _dal;
		private readonly IDqQuerySqlProvider _queryProvider;
		private readonly IConfigProvider _configProvider;

		public BasicQueryExecuter(IDal dal, IDqQuerySqlProvider queryProvider, IConfigProvider configProvider)
		{
			_dal = dal;
			_queryProvider = queryProvider;
			_configProvider = configProvider;
		}

		/// <summary>
		/// Executes the query agianst the data source. More than one table in the query is not supported.
		/// </summary>
		/// <param name="dataSource">The data source.</param>
		/// <param name="query">The query.</param>
		/// <returns></returns>
		public DataTable ExecuteQuery(IDataSource dataSource, IDqQuery query)
		{
			if (!TestDsContainsSchema(dataSource.Schema, query))
				throw new InvalidOperationException(String.Format("Schema does not contain all the columns in query"));

			var tableName = FindTableNameForQuery(dataSource.Schema, query);
			var sql = _queryProvider.GetSqlQuery(String.Format("{0}_{1}", dataSource.Name, tableName), query);
			return _dal.GetDataTable(_configProvider.ConnectionString, sql);
		}

		private static string FindTableNameForQuery(Schema schema, IDqQuery query)
		{
			var targetCol = query.Projections.First().Column.ColumnName;
			return schema.DataSet.Tables.Cast<DataTable>()
				.First(t => t.Columns.Cast<DataColumn>()
								.Any(c => c.ColumnName == targetCol))
				.TableName;
		}

		/// <summary>
		/// TODO: This function does not support same columns names in different tables
		/// </summary>
		private static bool TestDsContainsSchema(Schema schema, IDqQuery query)
		{
			var allColumnNames = query.Projections.Select(p => p.Column.ColumnName)
				.Concat(query.Selections.Select(s => s.Item2.GetConditionOperand1()))
				.Distinct();
			var allSchemaColumns = schema.DataSet.Tables.Cast<DataTable>()
				.SelectMany(t => t.Columns.Cast<DataColumn>().Select(c => c.ColumnName))
				.Distinct();
			return allColumnNames.All(c => allSchemaColumns.Contains(c));
		}
	}
}